package me.chan.nio.channel;

import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import static me.chan.nio.util.ByteBufferUtils.debugRead;
import static me.chan.nio.util.ByteBufferUtils.display;

@Slf4j
public class Server {

    public static void main(String[] args) {

        try {
    //  non-blocking without selector
        /*

            ByteBuffer buffer = ByteBuffer.allocate(1024);
            ServerSocketChannel serverChannel = ServerSocketChannel.open();
            serverChannel.configureBlocking(false);     // non-blocking mode
            serverChannel.bind(new InetSocketAddress(8080));
            // A key representing the registration of this channel with the given selector
            SelectionKey serverChannelKey = serverChannel.register(selector, 0 , null);
            serverChannelKey.interestOps(SelectionKey.OP_ACCEPT);
            List<SocketChannel> connectedList = new LinkedList<>();
            while (true) {
                SocketChannel socketChannel = serverChannel.accept();
                if (socketChannel != null) {
                    log.info("{} is connected", socketChannel);
                    socketChannel.configureBlocking(false);
                    connectedList.add(socketChannel);
                }

                for (SocketChannel sc : connectedList) {
                    int read = sc.read(buffer);            // non-blocking
                    if (read > 0) {
                        buffer.flip();
                        debugRead(buffer);
                        buffer.clear();
                    }
                }
            }*/

            Selector selector = Selector.open();
            ServerSocketChannel serverChannel = ServerSocketChannel.open();
            serverChannel.configureBlocking(false);     // non-blocking mode
            serverChannel.bind(new InetSocketAddress(8080));
            // A key representing the registration of this channel with the given selector
            SelectionKey serverChannelKey = serverChannel.register(selector, 0 , null);
            serverChannelKey.interestOps(SelectionKey.OP_ACCEPT);

            while (true) {
                selector.select();      // will block until any channel are ready

                Iterator<SelectionKey> iter =  selector.selectedKeys().iterator();
                while (iter.hasNext()) {
                    SelectionKey key = iter.next();
                    // remember to remove the key and then deal with it
                    iter.remove();
                    if (key.isAcceptable()) {
                        ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
                        SocketChannel socketChannel = ssc.accept();
                        log.info("connected channel is {}", socketChannel);
                        ByteBuffer buffer = ByteBuffer.allocate(8);
                        socketChannel.configureBlocking(false);     // non-blocking
                        // socketChannel.register(selector, SelectionKey.OP_READ);
                        // when socket channel register to the selector, make the buffer to be an attachment of selectionKey
                         socketChannel.register(selector, SelectionKey.OP_READ, buffer);
                    } else if (key.isReadable()) {
                        try {
                            SocketChannel socketChannel = (SocketChannel) key.channel();
                            // Make the ByteBuffer an attachment of this socket channel
                            // ByteBuffer buffer = ByteBuffer.allocate(8);

                            ByteBuffer buffer = (ByteBuffer) key.attachment();
                            int read = socketChannel.read(buffer);
                            if (read == -1) {       // it means that socket closes normally
                                key.cancel();
                            } else {
                                splitByLineSeparator(buffer);
                                if (buffer.position() == buffer.limit()) {  // need to resize
                                    ByteBuffer byteBuffer = ByteBuffer.allocate(buffer.capacity() * 2);
                                    buffer.flip();
                                    byteBuffer.put(buffer);
                                    // reattach to the selection key
                                    // invoke this method causes any previous attachment to be discarded
                                    key.attach(byteBuffer);
                                }
//                                buffer.flip();
//                                String content = StandardCharsets.UTF_8.decode(buffer).toString();
//                                log.info("the content from socket channel is :{}", content);
//                                debugRead(buffer);
                            }
                        } catch (IOException ex) {
                            log.error("{}", ex.getLocalizedMessage());
                            key.cancel();   // remove key from the set of selector
                        }
                    }

                }
            }

        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    private static void splitByLineSeparator(ByteBuffer src) {
        src.flip();
        for (int i = 0; i < src.limit(); i++) {
            if (src.get(i) == '\n') {
                int len = i - src.position() + 1;
                ByteBuffer dest = ByteBuffer.allocate(len);
                for (int j = 0; j < len; j++) {
                    dest.put(src.get());
                }
                display(dest);
            }
        }
        src.compact();
    }
}
